﻿// ------------------------------------------------------------------------------------------------
//  <copyright file="Similarity.cs" company="Iveely">
//    Copyright (c) Iveely Liu.  All rights reserved.
//  </copyright>
//  
//  <Create Time>
//    03/02/2013 21:59 
//  </Create Time>
//  
//  <contact owner>
//    liufanping@iveely.com 
//  </contact owner>
//  -----------------------------------------------------------------------------------------------

#region

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using IveelySE.NLP.Common.DataStructure;
using IveelySE.NLP.Common.DataStructure.Tree;

#endregion

namespace IveelySE.NLP.Knowledge.Extraction
{
    /// <summary>
    ///   文本字符相似度计算
    /// </summary>
    public class Similarity
    {
        #region 属性 OR 字段

        /// <summary>
        ///   相似度计算常量
        /// </summary>
        private const double Alpha = 1.6;

        /// <summary>
        ///   单例模式
        /// </summary>
        private static readonly Similarity similarity = new Similarity();

        /// <summary>
        ///   义原集
        /// </summary>
        private readonly Phrase _sememeCollection;

        /// <summary>
        ///   义原树
        /// </summary>
        private readonly MultiTree<string> _sememeTree;

        #endregion

        #region 公有方法

        /// <summary>
        ///   获取唯一实例
        /// </summary>
        /// <returns> </returns>
        public static Similarity GetInstance()
        {
            return similarity;
        }


        /// <summary>
        ///   获取两个字符串(按词组)的相似度值
        /// </summary>
        /// <param name="source"> 源字符串 </param>
        /// <param name="target"> 目标字符串 </param>
        /// <returns> 返回相似度值 </returns>
        public double GetByKeyword(string source, string target)
        {
            if(source.Equals(target))
            {
                return 1.0;
            }
            //获取源字符串的义原
            List<string> sourceSememes = this._sememeCollection.GetSememes(source);
            //获取目标字符串的义原
            List<string> targetSememes = this._sememeCollection.GetSememes(target);
            if(sourceSememes == null || targetSememes == null)
            {
                return 0.0;
            }
            double value = (from sourceSememe in sourceSememes
                            from targetSememe in targetSememes
                            select Alpha/(Alpha + 1000*this.GetDistanceOfSememe(sourceSememe, targetSememe))).Sum();
            return Math.Sqrt(value/((sourceSememes.Count*targetSememes.Count)));
        }

        /// <summary>
        ///   获取两个字符串(按句子)的相似度值
        /// </summary>
        /// <param name="source"> 源句子 </param>
        /// <param name="target"> 目标句子 </param>
        /// <param name="delimiter"> 分隔符 </param>
        /// <returns> 返回相似度值 </returns>
        public double GetBySentence(string source, string target, char[] delimiter)
        {
            Console.Write(source + ":" + target + " ");
            if(source.Equals(target))
            {
                return 1.0;
            }
            string[] sourceKeywords = source.Split(delimiter, StringSplitOptions.RemoveEmptyEntries);
            string[] targetKeywords = target.Split(delimiter, StringSplitOptions.RemoveEmptyEntries);
            double value = (from sourceKeyword in sourceKeywords
                            from targetKeyword in targetKeywords
                            select this.GetByKeyword(sourceKeyword, targetKeyword)).Sum();
            return Math.Sqrt(value*Alpha/(sourceKeywords.Length*targetKeywords.Length));
        }

        #endregion

        #region 私有方法

        private Similarity()
        {
            this._sememeTree = new MultiTree<string>();
            this._sememeCollection = new Phrase();
            this.Initialize();
        }

        /// <summary>
        ///   初始化方法
        /// </summary>
        private void Initialize()
        {
            //1. 初始化义原树
            this.CreateSememesTree();
            //2. 初始化义原集
            this.CreateSememesCollection();
        }

        /// <summary>
        ///   创建义原树
        /// </summary>
        private void CreateSememesTree()
        {
            string[] lines = File.ReadAllLines(@"Resource\HowNet\WHOLE_UTF8.DAT");
            foreach(var line in lines)
            {
                string[] info = line.Split(new[] {" "}, StringSplitOptions.RemoveEmptyEntries);
                int id = int.Parse(info[0]);
                string value = info[1].Split(new[] {"|"}, StringSplitOptions.RemoveEmptyEntries)[1];
                int parentId = int.Parse(info[2]);
                this._sememeTree.AddNode(id, value, parentId);
            }
        }

        /// <summary>
        ///   获取义原距离
        /// </summary>
        /// <param name="source"> 源义原 </param>
        /// <param name="target"> 目标义原 </param>
        /// <returns> 二者距离 </returns>
        private double GetDistanceOfSememe(string source, string target)
        {
            //如果相等，则距离视为0
            if(source == target)
            {
                return 0.0;
            }
            //如果任何一个为空，则距离视为1
            if(source.Length == 0 || target.Length == 0)
            {
                return 1.0;
            }
            string[] sourcePaths = this._sememeTree.GetNodePathByValue(source).Split(new[] {'.'},
                                                                                     StringSplitOptions.
                                                                                         RemoveEmptyEntries);
            string[] targetPaths = this._sememeTree.GetNodePathByValue(target).Split(new[] {'.'},
                                                                                     StringSplitOptions.
                                                                                         RemoveEmptyEntries);
            //如果任何一个没有找到
            if(sourcePaths.Length == 0 || targetPaths.Length == 0)
            {
                return 1.0;
            }
            int minLength = Math.Min(sourcePaths.Length, targetPaths.Length);
            //预想的最大距离
            int length = sourcePaths.Length + targetPaths.Length;
            for(int i = 0; i < minLength; i++)
            {
                if(sourcePaths[i] == targetPaths[i])
                {
                    length = length - 2;
                }
                else
                {
                    break;
                }
            }
            return length;
        }

        /// <summary>
        ///   创建义原集
        ///   (主要的词语对应的义原)
        /// </summary>
        private void CreateSememesCollection()
        {
            string[] lines = File.ReadAllLines(@"Resource\HowNet\GLOSSARY_UTF8.dat");
            foreach(var line in lines)
            {
                this._sememeCollection.Add(line, " ");
            }
        }

        #endregion

        #region 测试

        public static void Test()
        {
            Similarity instance = GetInstance();
            Console.WriteLine(instance.GetByKeyword("男人", "男人"));
            Console.WriteLine(instance.GetByKeyword("男人", "女人"));
            Console.WriteLine(instance.GetByKeyword("男人", "父亲"));
            Console.WriteLine(instance.GetByKeyword("男人", "母亲"));
            Console.WriteLine(instance.GetByKeyword("男人", "和尚"));
            Console.WriteLine(instance.GetByKeyword("男人", "经理"));
            Console.WriteLine(instance.GetByKeyword("男人", "高兴"));
            Console.WriteLine(instance.GetByKeyword("男人", "收音机"));
            Console.WriteLine(instance.GetByKeyword("男人", "鲤鱼"));
            Console.WriteLine(instance.GetByKeyword("男人", "工作"));
            Console.WriteLine(instance.GetByKeyword("男人", "责任"));
            Console.WriteLine(instance.GetByKeyword("男人", "香水"));
            Console.WriteLine(instance.GetByKeyword("香精", "香水"));
            Console.WriteLine(instance.GetByKeyword("男人", "丈夫"));
            Console.WriteLine(instance.GetBySentence("这 本 图书 作者 是 你", "你 很 爱 我 我 知道 ", new[] {' '}));
            Console.WriteLine(instance.GetBySentence("我 很 爱 你 你 知道 吗 ", "我 很 爱 你 我 知道 的", new[] {' '}));
            Console.WriteLine(instance.GetBySentence("我 很 爱 你 你 知道 吗 ", "我 很 爱 你 你 知道 吗 ", new[] {' '}));
            Console.WriteLine(instance.GetBySentence("你 的 爸爸 是 谁", "你 的 爸爸 的 爸爸 是 谁", new[] {' '}));
            Console.WriteLine(instance.GetBySentence("你 的 爸爸 是 谁", "你 的 妈妈 的 爸爸 是 谁", new[] {' '}));
            Console.WriteLine(instance.GetBySentence("你 的 爸爸 是 谁", "你 的 妈妈 的 妈妈 是 谁", new[] {' '}));
            Console.WriteLine(instance.GetBySentence("他 是 一 门 编程 语言", "他 是 一 门 优秀 的 编程 语言", new[] {' '}));
            Console.WriteLine(instance.GetBySentence("今天 天气 真好", "天气 很好 今天", new[] {' '}));
            Console.WriteLine(instance.GetBySentence("小明 睡觉 了 吗", "我 准备 打球 了", new[] {' '}));
            Console.WriteLine(instance.GetBySentence("今天 吃 什么 呢", "妈妈 买菜 去 了", new[] {' '}));
            Console.WriteLine(instance.GetBySentence("我们 相爱 了", "我们 谈 恋爱 了", new[] {' '}));
            Console.WriteLine(instance.GetBySentence("保护 地球 珍爱 生命", "我们 谈 恋爱 了", new[] {' '}));
            Console.WriteLine(instance.GetBySentence("香港 是 一个 法制 社会", "我们 的 社会 需要 法制", new[] {' '}));
        }

        #endregion
    }
}